package engine;

import java.io.File;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchResult;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.AddImport;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLDatatype;
import org.semanticweb.owlapi.model.OWLImportsDeclaration;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLNamedIndividual;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.model.OWLProperty;
import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;
import org.semanticweb.owlapi.model.PrefixManager;
import org.semanticweb.owlapi.util.DefaultPrefixManager;

/**
 *
 * @author Pospishnyi Oleksandr
 */
public class OntologyBuilder {

    private String tBoxFile;
    private String outputFile;
    private String ontologyIRI;
    private OWLOntologyManager manager;
    private OWLDataFactory factory;
    private OWLOntology ontology;
    private OWLOntology tBox;
    private PrefixManager tBoxPrefix;
    private PrefixManager currentPrefix;
    static Pattern upperLetterPattern = Pattern.compile("(?=\\p{Lu})");
    static HashMap<String, OWLClass> glueClasses;

    public OntologyBuilder(String tBoxFile, String outputFile, String ontologyIRI) {
        this.tBoxFile = tBoxFile;
        this.outputFile = outputFile;
        this.ontologyIRI = ontologyIRI;
        this.manager = OWLManager.createOWLOntologyManager();
        this.factory = manager.getOWLDataFactory();
    }

    public void createNewEmptyOntology() {
        try {
            ontology = manager.createOntology(IRI.create(ontologyIRI));
            tBox = manager.loadOntologyFromOntologyDocument(new File(tBoxFile));
            OWLImportsDeclaration glueImport = factory.getOWLImportsDeclaration(tBox.getOntologyID().getOntologyIRI());
            manager.applyChange(new AddImport(ontology, glueImport));
            manager.saveOntology(ontology, IRI.create(new File(outputFile)));
            tBoxPrefix = new DefaultPrefixManager(tBox.getOntologyID().getOntologyIRI().toString() + "#");
            currentPrefix = new DefaultPrefixManager(ontology.getOntologyID().getOntologyIRI().toString() + "#");
        } catch (Exception ex) {
            Logger.getLogger(OntologyBuilder.class.getName()).log(Level.SEVERE, null, ex);
        }

        glueClasses = new HashMap<String, OWLClass>(200);
        for (OWLClass glueClass : tBox.getClassesInSignature(false)) {
            glueClasses.put(glueClass.getIRI().getFragment(), glueClass);
        }

    }

    public void populate(NamingEnumeration<SearchResult> entityList) throws NamingException {
	    HashSet stubSet = new HashSet();
	    HashSet postSet = new HashSet();
        if (entityList != null) {
            int i = 1;
            while (entityList.hasMore()) {
                SearchResult entity = entityList.next();
                Attributes attributes = entity.getAttributes();

                OWLNamedIndividual instance = null;
                OWLClass instanceClass = null;
                Attribute objectClass = attributes.get("objectClass");
                String id = null;

                for (int j = 0; j < objectClass.size(); j++) {
                    Attribute attr = attributes.get(objectClass.get(j).toString() + "ID");
                    if (attr != null) {
                        id = attr.get(0).toString();
                        instance = factory.getOWLNamedIndividual(IRI.create(currentPrefix.getDefaultPrefix(), id));
                    }
                }

                if (instance == null) {
                    Logger.getLogger(OntologyBuilder.class.getName()).log(Level.SEVERE,
                            "No entity ID detected :", instance.getIRI());
                    return;
                }
		
		Logger.getLogger(OntologyBuilder.class.getName()).log(Level.INFO, "+ " + instance.getIRI());

                if (objectClass.size() == 1) {
                    instanceClass = factory.getOWLClass(objectClass.get().toString().substring(5), tBoxPrefix);
                } else if (objectClass.size() == 2) {
                    OWLClass classOne = factory.getOWLClass(objectClass.get(0).toString().substring(5), tBoxPrefix);
                    OWLClass classTwo = factory.getOWLClass(objectClass.get(1).toString().substring(5), tBoxPrefix);
                    if (classTwo.getSuperClasses(tBox).contains(classOne)) {
                        instanceClass = classTwo;
                    } else if (classOne.getSuperClasses(tBox).contains(classTwo)) {
                        instanceClass = classOne;
                    } else {
                        Logger.getLogger(OntologyBuilder.class.getName()).log(Level.SEVERE,
                                "objectClass sould NOT contain completly different classes! :", objectClass);
                        return;
                    }
                } else {
                    Logger.getLogger(OntologyBuilder.class.getName()).log(Level.SEVERE, "Unexprected objectClass entry: ", objectClass);
                    return;
                }

                if (ontology.containsIndividualInSignature(instance.getIRI())) {
		    postSet.add(instance.getIRI());
                } else {
                    manager.addAxiom(ontology, factory.getOWLClassAssertionAxiom(instanceClass, instance));
                }

                manager.addAxiom(ontology, factory.getOWLDataPropertyAssertionAxiom(
                        factory.getOWLDataProperty("ID", tBoxPrefix), instance, id));

                NamingEnumeration<String> attrIDs = attributes.getIDs();
                while (attrIDs.hasMore()) {
                    Attribute nextAttribute = attributes.get(attrIDs.next());
                    String name = nextAttribute.getID();
                    if (name.startsWith("GLUE2")) {
                        StringBuilder classNameBuilder = new StringBuilder();
                        String[] tokens = upperLetterPattern.split(name.substring(5));
                        int tn = 1;
                        boolean matchFound = false;
                        while (!matchFound) {
                            classNameBuilder.append(tokens[tn++]);
                            if (glueClasses.containsKey(classNameBuilder.toString())) {
                                matchFound = true;
                            }
                        }

                        OWLClass glueClass = factory.getOWLClass(classNameBuilder.toString(), tBoxPrefix);
                        IRI propertyIRI = IRI.create(tBoxPrefix.getDefaultPrefix(), name.substring(5 + classNameBuilder.length()));
                        OWLProperty property = null;
                        if (tBox.containsDataPropertyInSignature(propertyIRI)) {
                            property = factory.getOWLDataProperty(propertyIRI);
                            for (OWLSubClassOfAxiom axiom : tBox.getSubClassAxiomsForSubClass(glueClass)) {
                                if (axiom.getDataPropertiesInSignature().contains(property)) {
                                    Set<OWLDatatype> datatypesInSignature = axiom.getSuperClass().getDatatypesInSignature();
                                    if (datatypesInSignature.size() != 1) {
                                        Logger.getLogger(OntologyBuilder.class.getName()).log(Level.SEVERE, "Unrecognized datatype in TBox axiom: ", axiom);
                                        return;
                                    } else {
                                        OWLDatatype datatype = datatypesInSignature.iterator().next();
                                        for (int j = 0; j < nextAttribute.size(); j++) {
                                            OWLLiteral literalValue = factory.getOWLLiteral(nextAttribute.get(j).toString(), datatype);
                                            manager.addAxiom(ontology, factory.getOWLDataPropertyAssertionAxiom(property.asOWLDataProperty(), instance, literalValue));
                                        }
                                    }
                                }
                            }
                        } else if (tBox.containsObjectPropertyInSignature(propertyIRI)) {
                            property = factory.getOWLObjectProperty(propertyIRI);
                            for (OWLSubClassOfAxiom axiom : tBox.getSubClassAxiomsForSubClass(glueClass)) {
                                if (axiom.getObjectPropertiesInSignature().contains(property)) {
                                    Set<OWLClass> classesInSignature = axiom.getSuperClass().getClassesInSignature();
                                    if (classesInSignature.size() != 1) {
                                        Logger.getLogger(OntologyBuilder.class.getName()).log(Level.SEVERE, "Unrecognized class in TBox axiom: ", axiom);
                                        return;
                                    } else {
                                        OWLClass classInAxiom = classesInSignature.iterator().next();
                                        for (int j = 0; j < nextAttribute.size(); j++) {
                                            IRI individualIRI = IRI.create(tBoxPrefix.getDefaultPrefix(), nextAttribute.get(j).toString().toLowerCase());
                                            if (tBox.containsIndividualInSignature(individualIRI)) {
                                                OWLNamedIndividual valueIndividual = factory.getOWLNamedIndividual(individualIRI);
                                                manager.addAxiom(ontology, factory.getOWLObjectPropertyAssertionAxiom(property.asOWLObjectProperty(), instance, valueIndividual));
                                            } else {
                                                //TODO: add closed enum check
                                                OWLNamedIndividual valueIndividual = factory.getOWLNamedIndividual(individualIRI);
                                                manager.addAxiom(tBox, factory.getOWLClassAssertionAxiom(classInAxiom, valueIndividual));
                                                manager.addAxiom(ontology, factory.getOWLObjectPropertyAssertionAxiom(property.asOWLObjectProperty(), instance, valueIndividual));
                                            }
                                        }
                                    }
                                }
                            }
                        } else {
                            if (name.endsWith("ForeignKey")) {
                                for (int j = 0; j < nextAttribute.size(); j++) {
                                    IRI individualIRI = IRI.create(currentPrefix.getDefaultPrefix(), nextAttribute.get(j).toString());
                                    OWLNamedIndividual valueIndividual = factory.getOWLNamedIndividual(individualIRI);;

                                    if (!ontology.containsIndividualInSignature(individualIRI)) {
                                        OWLClass stubClass = factory.getOWLClass(IRI.create(tBoxPrefix.getDefaultPrefix(), name.substring(5 + classNameBuilder.length(), name.length() - 10)));
                                        manager.addAxiom(ontology, factory.getOWLClassAssertionAxiom(stubClass, valueIndividual));
					stubSet.add(individualIRI);
                                    }

                                    boolean mathced = false;

                                    OWLClass valueClass = factory.getOWLClass(IRI.create(tBoxPrefix.getDefaultPrefix(), name.substring(5 + classNameBuilder.length(), name.length() - 10)));
                                    for (OWLSubClassOfAxiom axiom : tBox.getSubClassAxiomsForSubClass(glueClass)) {
                                        if (!valueClass.equals(glueClass) && axiom.getClassesInSignature().containsAll(Arrays.asList(valueClass, glueClass))) {
                                            OWLObjectProperty valueProperty = axiom.getObjectPropertiesInSignature().iterator().next();
                                            manager.addAxiom(ontology, factory.getOWLObjectPropertyAssertionAxiom(valueProperty, instance, valueIndividual));
                                            mathced = true;
                                        }
                                    }
                                    for (OWLSubClassOfAxiom axiom : tBox.getSubClassAxiomsForSubClass(valueClass)) {
                                        if (!valueClass.equals(glueClass) && axiom.getClassesInSignature().containsAll(Arrays.asList(valueClass, glueClass))) {
                                            OWLObjectProperty valueProperty = axiom.getObjectPropertiesInSignature().iterator().next();
                                            manager.addAxiom(ontology, factory.getOWLObjectPropertyAssertionAxiom(valueProperty, valueIndividual, instance));
                                            mathced = true;
                                        }
                                    }
                                    if (!mathced) {
                                        OWLObjectProperty relatedTo = factory.getOWLObjectProperty(IRI.create(tBoxPrefix.getDefaultPrefix(), "RelatedTo"));
                                        manager.addAxiom(ontology, factory.getOWLObjectPropertyAssertionAxiom(relatedTo, instance, valueIndividual));
                                    }
                                }
                            } else {
                                Logger.getLogger(OntologyBuilder.class.getName()).log(Level.SEVERE,
                                        "Unknown property:", propertyIRI);
                                return;
                            }
                        }

                    }
                }
            }
        }
	
	    HashSet sb = new HashSet(stubSet);
	    HashSet ps = new HashSet(postSet);

	    sb.removeAll(postSet);
	    ps.removeAll(sb);

	    if (!sb.isEmpty()) {
		    Logger.getLogger(OntologyBuilder.class.getName()).log(Level.WARNING,
			    "Unrecognized FK stubs: \n" + sb.toString());
	    }

	    if (!ps.isEmpty()) {
		    Logger.getLogger(OntologyBuilder.class.getName()).log(Level.WARNING,
			    "Duplicates detected: \n" +  ps.toString());
	    }

    }

    public void flush() {
        try {
            System.out.println("Saving to file....");
            manager.saveOntology(ontology, new FileOutputStream(new File(outputFile)));
        } catch (Exception ex) {
            Logger.getLogger(OntologyBuilder.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
